#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <zlib.h>
#include <vector>
#include <algorithm>
#include "router.h"
#include "main.h"
#include "nic.h"
#include "link.h"
#include "app.h"
#include "router_common.h"
#include "sim_result.h"
#include "string.h"
#include "defines.h"
#include "shared.h"
#include "batch.h"
#include "rank.h"

#ifdef TR_INTEG
#undef INVALID
#include "processor.h"
#include "globals.h"

#undef INVALID
#define INVALID 1

#endif

int GlobalRankMode=BATCHING_MODE_NONE;
int GlobalRankInterval=48000;
int rank_array[MAX_NODES];
bool smallest_first=true;
unsigned int RankingInterval=0;

double rank_metric_values[MAX_NODES];
double rank_metric_values_second[MAX_NODES];

double LastMetricValue1[MAX_NODES];
double LastMetricValue2[MAX_NODES];
double LastMetricValueSecond1[MAX_NODES];
double LastMetricValueSecond2[MAX_NODES];

void UpdateRankMatrix()
{
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  //Asssign the calculated ranks to the global_priority_matrix
  /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
  for(int node=0; node<NUM_NODES; node++)
    for(int pri_level=0; pri_level<NUM_NODES; pri_level++)
    {
      global_priority_matrix[node][pri_level] = rank_array[pri_level]; 
      //Global
      if(ranking_type[node] == RANKING_GLOBAL_STATIC_RR) 
        global_priority_matrix[node][pri_level] =  (global_priority_matrix[node][pri_level] + 1) % num_priority_levels;
      else if(ranking_type[node] == RANKING_GLOBAL_PROX_AWARE) 
        global_priority_matrix[node][pri_level] = calc_dist(node,pri_level);
      else if(ranking_type[node] == RANKING_GLOBAL_RANDOM) 
        global_priority_matrix[node][pri_level] = node == 0 ? rand()%num_priority_levels : global_priority_matrix[0][pri_level]; 
      else if(ranking_type[node] == RANKING_GLOBAL_BASE) 
        global_priority_matrix[node][pri_level] = 0; 
      else if(ranking_type[node] == RANKING_GLOBAL_STATIC_PREDEFINED) 
        global_priority_matrix[node][pri_level] =  ranking_vector_Static[pri_level];
      else if(ranking_type[node] == RANKING_GLOBAL_OS_PRIORITY) 
        global_priority_matrix[node][pri_level] =  ranking_OS_Weights[pri_level];

      //Local
      else if(ranking_type[node] == RANKING_LOCAL_STATIC_RR) 
        global_priority_matrix[node][pri_level] = (global_priority_matrix[node][pri_level] + 1) % MAX_PRIORITY_LEVELS;
      else if(ranking_type[node] == RANKING_LOCAL_PROX_AWARE) 
        global_priority_matrix[node][pri_level] = calc_dist(node,pri_level);
      else if(ranking_type[node] == RANKING_LOCAL_RANDOM) 
        global_priority_matrix[node][pri_level] = rand()%num_priority_levels; 
      else if(ranking_type[node] == RANKING_LOCAL_BASE) 
        global_priority_matrix[node][pri_level] = 0; 
    }

  RankingInterval++;
  
  
  printf("Rank Array %d\n", RankingInterval);
  for(int node=0; node<NUM_NODES; node++)
  {
    printf("%d ",global_priority_matrix[0][node]); 
    if(!((node+1)%MESH_NUM_COLS))
      printf("\n");
  }
}

void GetRankMetricValues()
{

  static bool first_rank_set = false;

  int quantize = -1;
  int FIRST_RANK_INTERVAL = 10000;

   if(   GlobalRankMode == RANKING_GLOBAL_NAST_PRIORITY
      || GlobalRankMode == RANKING_GLOBAL_HYBRID_INST_NAST)
     //|| GlobalRankMode == RANKING_GLOBAL_STALL_CRITICAL )
     FIRST_RANK_INTERVAL = 50000;

  if(first_rank_set == true && global_clock && global_clock % GlobalRankInterval)
    return;
  if(first_rank_set == false && global_clock < FIRST_RANK_INTERVAL)
    return;


  //*****************************************************
  //Gather Ranking Metric Values from all nodes
  //*****************************************************
  printf("Getting rank metric values at clock %lld\n",sim_clock);
  for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
  {

    double val = 0, val_second=0, val1=0, val2=0;

    //***************************************
    if(GlobalRankMode == RANKING_GLOBAL_STALL_FAIRNESS)
    { 
      //double STshare = processor[n].returnStallSharedThisInterval();
      //double STalone = processor[n].returnStallThisInterval();
      int pass = processor[n].pass;
      double STshare = processor[n].network_stall_cycles - LastMetricValue1[n];
      double STalone = processor[n].network_stall_alone - LastMetricValue2[n];
      val = STshare/STalone;
      if(strstr(processor[n].app_name,"patternHT"))
        val = processor[n].returnThptSlowdown(GlobalRankInterval);
      
      LastMetricValue1[n] = processor[n].network_stall_cycles;
      LastMetricValue2[n] = processor[n].network_stall_alone;
      
      smallest_first = false;
      quantize = 1;
    }

    
    //***************************************
    else if(GlobalRankMode == RANKING_GLOBAL_STALL_FAIRNESS_ORACLE)
    { 
      int pass = processor[n].pass;
      double STshare = processor[n].network_stall_cycles - LastMetricValue1[n];
      double STalone = processor[n].oracleNastCurrent[pass]*(processor[n].l1misses - LastMetricValue2[n]);
      val = STshare/STalone;
      if(strstr(processor[n].app_name,"patternHT"))
        val = processor[n].returnThptSlowdown(GlobalRankInterval);
      
      LastMetricValue1[n] = processor[n].network_stall_cycles;
      LastMetricValue2[n] = processor[n].l1misses;
      
      smallest_first = false;
      quantize = 1;
    }
    //***************************************
    else if(GlobalRankMode == RANKING_GLOBAL_ROB_SJF)
    {
      if(first_rank_set == false) 			
        val = processor[n].sum_sjf_values/sim_clock;
      else
      {
        val = processor[n].sum_sjf_values/GlobalRankInterval;
        processor[n].sum_sjf_values = 0;
      }
      smallest_first = true;
      quantize = 1;
    }
    //***************************************
    else if(GlobalRankMode == RANKING_GLOBAL_INSTR_SJF)
    {
      val = 100*((processor[n].l1misses-LastMetricValue1[n])/
                 (processor[n].retired_instructions-LastMetricValue2[n]));

      if((processor[n].retired_instructions-LastMetricValue2[n]) == 0)
        val  = rank_metric_values[n];
      LastMetricValue1[n] = processor[n].l1misses;
      LastMetricValue2[n] = processor[n].retired_instructions;
      smallest_first = true;
      quantize = 1;
    }
    //***************************************
    else if(GlobalRankMode == RANKING_GLOBAL_NAST_PRIORITY)
    {
       val1 = processor[n].network_stall_cycles - LastMetricValue1[n];
       val2 = processor[n].l1misses - LastMetricValue2[n];
       val = val1/val2;
       if(val1 == 0 || val2 == 0)
         val = processor[n].curr_nast[processor[n].pass-1];
      LastMetricValue1[n] = processor[n].network_stall_cycles;
      LastMetricValue2[n] = processor[n].l1misses;
      smallest_first = false;
      quantize = 1;
    }
    //***************************************
    else if(GlobalRankMode == RANKING_GLOBAL_HYBRID_INST_NAST)
    {
      val = 100*((processor[n].l1misses-LastMetricValue1[n])/
                 (processor[n].retired_instructions-LastMetricValue2[n]));

      if((processor[n].retired_instructions-LastMetricValue2[n]) == 0)
        val  = rank_metric_values[n];
      LastMetricValue1[n] = processor[n].l1misses;
      LastMetricValue2[n] = processor[n].retired_instructions;

      val1 = processor[n].network_stall_cycles - LastMetricValueSecond1[n];
      val2 = processor[n].l1misses - LastMetricValueSecond2[n];
      val_second = val1/val2;
      if(val1 == 0 || val2 == 0)
        val_second = processor[n].curr_nast[processor[n].pass-1];
      LastMetricValueSecond1[n] = processor[n].network_stall_cycles;
      LastMetricValueSecond2[n] = processor[n].l1misses;


      smallest_first = true;
      quantize = 2;
    }
    //***************************************
    else if(GlobalRankMode == RANKING_GLOBAL_STALL_CRITICAL)
    {
      val = 100*((processor[n].l1misses-LastMetricValue1[n])/
                 (processor[n].retired_instructions-LastMetricValue2[n]));

      if((processor[n].retired_instructions-LastMetricValue2[n]) == 0)
        val  = rank_metric_values[n];
      LastMetricValue1[n] = processor[n].l1misses;
      LastMetricValue2[n] = processor[n].retired_instructions;

      val1 = processor[n].l2misses - LastMetricValueSecond1[n];
      val2 = processor[n].retired_instructions - LastMetricValueSecond2[n];
      val_second = 100*val1/val2;
      if(val1 == 0 || val2 == 0)
        val_second = processor[n].curr_nast[processor[n].pass-1];
      LastMetricValueSecond1[n] = processor[n].l2misses;
      LastMetricValueSecond2[n] = processor[n].retired_instructions;

      smallest_first = true;
      quantize = 2;
    }

    //***************************************
    //else if(GlobalRankMode == RANKING_LOCAL_SJF)
      //val = local_sjf_counter[node][n];


    rank_metric_values[n] = val;
    rank_metric_values_second[n] = val_second;

    // Print Priority Ranking Metric Values       
    printf("%5.2f ",val); 
    if(!((n+1)%MESH_NUM_COLS))
      printf("\n");
  }
  if(first_rank_set == false)
    first_rank_set = true;

  if(quantize == 1)
    QuantizeRankValues();
  else if(quantize == 2)
    QuantizeRankTwoTierValues();

  UpdateRankMatrix();
  return;
}
void QuantizeRankValues()
{
  //****************************************************
  //Calculate the ranks by kmean clustering quantization
  //****************************************************
  float values[64]; 
  int  classes[64] ={0};
  for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
    values[n] = rank_metric_values[n];
  kmean_algo(values, classes, num_priority_levels,64,1.25);
  for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
  {
    if(smallest_first == true)
      classes[n] = num_priority_levels - classes[n] - 1; 
    rank_array[n] = classes[n];
    classes[n] = 0;
    //rank_array[n] = ranking_vector_Static[n];
  }
  
 
  kmean_algo(values, classes, MAX_MONITOR_INTERVALS,64,1.25);
  for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
  {
    if(smallest_first == true)
      classes[n] = MAX_MONITOR_INTERVALS - classes[n] - 1; 
    global_throttle_array[n] = classes[n];
  }
}

void QuantizeRankTwoTierValues()
{
  //****************************************************
  //Calculate the ranks by kmean clustering quantization
  //****************************************************
  float values[64]; 
  int  classes[64] ={0};
  int num_second_priority_levels=2;
  
  float values_second[16][64]; 
  static int  classes_second[16][64]={0};
 
  for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
  {
    values[n] = rank_metric_values[n];
    classes[n] = 0;
  }
  kmean_algo(values, classes, num_priority_levels/num_second_priority_levels,64,1.25);
  
  int np = num_priority_levels/num_second_priority_levels;
  for(int c=0; c< np; c++)
  {
    int k=0;
    for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
      if(classes[n] == c)
      {
        values_second[c][k] = rank_metric_values_second[n];
        k++;
      }
    //implicitly assume second tier smallest first is false (coded for NAST)
    //FIXME if second tier rank is different
    //if(RankingInterval%2)
    printf("KMEAN for class %d members %d\n",c,k);
    if(k)
      kmean_algo(values_second[c], classes_second[c], num_second_priority_levels,k,2);
  }

  int class_array[MAX_NODES];
  
  for(int c=0; c< np; c++)
  {
    int k=0; 
    for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
      if(classes[n] == c)
      {
        rank_array[n] = (smallest_first == true ? (np - c - 1) : c) * num_second_priority_levels + classes_second[c][k];
        class_array[n] = c * num_second_priority_levels + classes_second[c][k];
        k++;
      }
  }
  printf("CLASS array \n");
  for(int n=0; n< MAX_PRIORITY_LEVELS; n++)
  {
    printf("%d ",class_array[n]);
    if((n+1)%8 == 0)
      printf("\n");
  }

  ClassPromotion(rank_array,num_priority_levels,64);

}


void kmean_algo(float values[64], int classes[64], int num_classes, int num_values, float merge_threshold)
{
  float centroids[32] = {0};
  int members[64] = {0};
  int iter = 0;
  int min_distance = 0, min_class=0;
  float min=10000000, max=0;
  // Do four iterations
  for(int var=0; var<num_values; var++)
  {
    if(values[var] == 1)
      continue;
    min = min > values[var] ? values[var]: min;
    max = max < values[var] ? values[var]: max;
  }

  for(int c=0; c<num_classes; c++)
    centroids[c] = min + c*((max-min)/(float)num_classes);

  /*if(max-min < 4)
    {
    for(int var=0; var<num_values; var++)
    values[var] = values[var]/min;
    min = 1; max= max/min;
    }*/

  /***PRINT***/
  printf("KMEAN Values :\n");
  for(int var=0; var<num_values; var++)
  {
    printf("%4.2f ",values[var]);
    if(((var+1)%8)==0)
      printf("\n");
  }
  printf("KMEAN Centroids : ");
  for(int c=0; c<num_classes; c++)
    printf("%4.2f ",centroids[c]);
  printf("\n");
  /***PRINT***/



  while(iter < 4)
  {
    for(int var=0; var<num_values; var++)
    {
      min_distance = 100000;
      for(int c=0; c<num_classes; c++)
        if(min_distance > abs((int)(values[var]-centroids[c])))
        {
          min_distance =  abs((int)(values[var]-centroids[c]));
          min_class = c;
        }
      classes[var] = min_class;
    }
    for(int c=0; c<num_classes; c++)
    {
      float avg = 0;
      int members = 0;
      for(int var=0; var<num_values; var++)
      {
        if(classes[var] == c)
        {avg += values[var]; members++;}
      }
      avg = members ? avg/members : centroids[c];
      centroids[c]=avg;
    }
    iter++;
  }
  //Class promotion if next class centroid is very close to current class
  //centroid
  for(int var=0; var<num_values; var++)
  {
    int c = classes[var];
    if(c<num_classes-1 && centroids[c+1]/centroids[c] < merge_threshold)
      classes[var] = c+1;
    members[classes[var]]++;
  }
  //Class promotion if next class is empty
  for(int c=0; c<num_classes-1; c++)
    if(members[c+1] == 0)
    {
      for(int var=0; var<num_values; var++)
        if(classes[var] == c)
          classes[var] = c+1;
    }

  /***PRINT***/
  printf("KMEAN Classes :\n");
  for(int var=0; var<num_values; var++)
  {
    printf("%d ",classes[var]);
    if((var+1)%8 == 0)
      printf("\n");
  }
  printf("Exiting KMEAN\n");
  /***PRINT***/
}

void ClassPromotion(int classes[64], int num_classes, int num_values)
{
  int members[64] = {0};
  //Class promotion if next class centroid is very close to current class
  //centroid
  for(int var=0; var<num_values; var++)
    members[classes[var]]++;
  //Class promotion if next class is empty
  for(int c=0; c<num_classes-1; c++)
    if(members[c+1] == 0)
    {
      for(int var=0; var<num_values; var++)
        if(classes[var] == c)
          classes[var] = c+1;
    }
}
